package com.joyveb.securitycheck.util;

import java.io.ByteArrayOutputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.Cipher;

public class RSAComponent {

	private static final String KEY_ALGORITHM = "RSA";
	private static final String SIGNATURE_ALGORITHM = "MD5withRSA";
	private static final String PUBLIC_KEY = "PublicKey";
	private static final String PRIVATE_KEY = "PrivateKey";
	private static final int KEY_SIZE = 1024;
	private static final int MIN_KEY_SIZE = 512;
	private static final int MAX_KEY_SIZE = 65536;
	private static final int MAX_DECRYPT_BLOCK = KEY_SIZE / 8;
	private static final int MAX_ENCRYPT_BLOCK = 117;

	private RSAComponent() {}
	
	public static Map<String,Key> genKey(int keySize) throws NoSuchAlgorithmException, IllegalAccessException{
		if(keySize < MIN_KEY_SIZE || keySize > MAX_KEY_SIZE){
			throw new IllegalAccessException("key size must be greater than 512 and less than 565536。");
		}
		if((keySize % 64)!=0){
			throw new IllegalAccessException(" key size must be a multiple of 64.");
		}
		KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
		keyPairGenerator.initialize(keySize);
		KeyPair keyPair = keyPairGenerator.generateKeyPair();
		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
		RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
		Map<String, Key> keyMap = new HashMap<String, Key>(2);
		keyMap.put(PUBLIC_KEY, publicKey);
		keyMap.put(PRIVATE_KEY, privateKey);
		return keyMap;
	}

	public static Map<String, Key> genKey() throws NoSuchAlgorithmException, IllegalAccessException {
		return RSAComponent.genKey(KEY_SIZE);
	}

	public static byte[] getPublicKey(Map<String, Key> keyMap) {
		return keyMap.get(PUBLIC_KEY).getEncoded();
	}

	public static byte[] getPrivateKey(Map<String, Key> keyMap) {
		return keyMap.get(PRIVATE_KEY).getEncoded();
	}

	public static byte[] encryptByPrivateKey(byte[] data, byte[] privateKey)
			throws Exception {
		PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
				privateKey);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key key = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.ENCRYPT_MODE, key);
		try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
			int page = MAX_ENCRYPT_BLOCK;
			for (int i = 0; i < data.length; i += page) {
				if ((data.length - i) < page) {
					page = data.length - i;
				}
				out.write(cipher.doFinal(data, i, page));
			}
			return out.toByteArray();
		}
	}

	public static byte[] decryptByPrivateKey(byte[] data, byte[] privateKey)
			throws Exception {
		PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
				privateKey);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key key = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.DECRYPT_MODE, key);
		try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
			int page = MAX_DECRYPT_BLOCK;
			for (int i = 0; i < data.length; i += page) {
				if ((data.length - i) < page) {
					page = data.length - i;
				}
				out.write(cipher.doFinal(data, i, page));
			}
			return out.toByteArray();
		}
	}

	public static byte[] encryptByPublicKey(byte[] data, byte[] publicKey)
			throws Exception {
		X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
				publicKey);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key key = keyFactory.generatePublic(x509EncodedKeySpec);
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.ENCRYPT_MODE, key);
		try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
			int page = MAX_ENCRYPT_BLOCK;
			for (int i = 0; i < data.length; i += page) {
				if ((data.length - i) < page) {
					page = data.length - i;
				}
				byte[] doFinal = cipher.doFinal(data, i, page);
				out.write(doFinal);
			}
			return out.toByteArray();
		}
	}

	public static byte[] decryptByPublicKey(byte[] data, byte[] publicKey)
			throws Exception {
		X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
				publicKey);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key key = keyFactory.generatePublic(x509EncodedKeySpec);
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.DECRYPT_MODE, key);
		try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
			int page = MAX_DECRYPT_BLOCK;
			for (int i = 0; i < data.length; i += page) {
				if ((data.length - i) < page) {
					page = data.length - i;
				}
				out.write(cipher.doFinal(data, i, page));
			}
			return out.toByteArray();
		}
	}
	
	public static byte[] sign(byte[] data, byte[] privateKey) throws Exception {
		PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
				privateKey);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		PrivateKey privateKey2 = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initSign(privateKey2);
		signature.update(data);
		return signature.sign();
	}
	
	public static boolean verify(byte[] data, byte[] publicKey, byte[] sign)
			throws Exception {
		X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKey);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		PublicKey publicKey2 = keyFactory.generatePublic(x509EncodedKeySpec);
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initVerify(publicKey2);
		signature.update(data);
		return signature.verify(sign);

	}

}
